/**
 * Trabalho PI - Filtros Espaciais
 *
 *  Thales Luis Rodrigues Sabino de Alcantara Bourbon Guacamole em Pedra Dura Tanto Bate Até Kifura
 *  Eder Knop de Castro Alves Moacir Junior Haduken Tek Tek Turugen
 *  Tássio de Almeida Perez Tiger Mocotó
 *
 */
#include "pixelMap.h"
#include "math.h"

#include <GL/gl.h>
#include <GL/glut.h>

#ifdef WIN32
    #include <windows.h>
    #include <GL/glui.h>
#else
    #include <glui.h>
#endif

#include <stdlib.h>
#include <stdio.h>
#include <iostream>
#include <math.h>

/**
 * Variáveis do GLUI
 */
#define GLUI_BUTTON_OP_ARITHIMETIC_LOAD  0x00
#define GLUI_BUTTON_OP_ARITHIMETIC_APPLY 0x01
#define GLUI_BUTTON_OP_LOGIC_LOAD        0x02
#define GLUI_BUTTON_OP_LOGIC_APPLY       0x03
#define GLUI_BUTTON_OP_GEOMETRIC_LOAD    0x04

#define GLUI_ROTATION_OP_GEOMETRIC    0x10
#define GLUI_TRANSLATION_OP_GEOMETRIC 0x11
#define GLUI_SCALE_OP_GEOMETRIC       0x12

#define GLUI_RADIO_OP              0xf0
#define GLUI_RADIO_OP_ARITHIMETIC  0xf1
#define GLUI_RADIO_OP_LOGIC        0xf3

GLUI *glui;

GLUI_Panel      *panel_operation;
GLUI_RadioGroup *panel_operation_radio_group;
int op;

GLUI_Panel      *panel_op_arithimetic;
GLUI_RadioGroup *panel_op_arithimetic_radio_group;
GLUI_String panel_op_arithimetic_image1;
GLUI_String panel_op_arithimetic_image2;
int op_arithimetic;

GLUI_Panel      *panel_op_logic;
GLUI_RadioGroup *panel_op_logic_radio_group;
GLUI_String panel_op_logic_image1;
GLUI_String panel_op_logic_image2;
int op_logic;

GLUI_Panel       *panel_op_geometric;
GLUI_String panel_op_geometric_image;
float panel_op_geometric_rotarion_matrix[16] = {
    1.0f, 0.0f, 0.0f, 0.0f,
    0.0f, 1.0f, 0.0f, 0.0f,
    0.0f, 0.0f, 1.0f, 0.0f,
    0.0f, 0.0f, 0.0f, 1.0f
};
float panel_op_geometric_translation_XY[2] = {1.0f, 1.0f};
float panel_op_geometric_scale[1]          = {1.0f};



PixelMap *img_orig1 = NULL;
PixelMap *img_orig2 = NULL;
PixelMap *img_dest  = NULL;

int main_window;
int viewer; //id da janela de visualização
float rotationMatrix[4];
int translationVector[2];
float scale;

uByte *data;

using namespace std;

void display() {
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   if( img_dest ) img_dest->ViewImage();
   glutSwapBuffers();
}

void key(unsigned char key, int x, int y) {
    switch (key) {
        case 27 :
        case 'q':
            exit(0);
        break;
    }

    glutPostRedisplay();
}

#define ARITHMETIC_OP_SUM 0x41
#define ARITHMETIC_OP_SUB 0x42
#define ARITHMETIC_OP_MID 0x43

void arithmetic_op_images( int which_op ) {
    if( img_dest ) delete img_dest;

    PixelMap* bigger_img;
    PixelMap* less_img;

    if((img_orig1->GetWidth()*img_orig1->GetHeight()) > (img_orig2->GetWidth()*img_orig2->GetHeight())) {
      bigger_img = img_orig1;
      less_img   = img_orig2;
    }
    else {
      bigger_img = img_orig2;
      less_img   = img_orig1;
    }

    int width  = bigger_img->GetWidth();
    int height = bigger_img->GetHeight();

    img_dest = new PixelMap();
    data = new uByte[ width * height * 3 ];
    img_dest->CreateImage( width, height, data );

    for( int y = 0; y < img_dest->GetHeight(); y++ ) {
        for( int x = 0; x < img_dest->GetWidth(); x++ ) {
            int index   = ( y * width + x ) * 3;
            int index2  = ( y * less_img->GetWidth() + x ) * 3;

            int r = 0;
            int g = 0;
            int b = 0;

            if( (y > less_img->GetHeight()) || (x > less_img->GetWidth()) ) {
              img_dest->data[index]   = bigger_img->data[index];
              img_dest->data[index+1] = bigger_img->data[index+1];
              img_dest->data[index+2] = bigger_img->data[index+2];
            }
            else {
              switch( which_op ) {
                  case ARITHMETIC_OP_SUM:
                      r = bigger_img->data[index]   + less_img->data[index2];
                      g = bigger_img->data[index+1] + less_img->data[index2+1];
                      b = bigger_img->data[index+2] + less_img->data[index2+2];

                      img_dest->data[index]   = ( r >= 255 ) ? 255 : r;
                      img_dest->data[index+1] = ( g >= 255 ) ? 255 : g;
                      img_dest->data[index+2] = ( b >= 255 ) ? 255 : b;
                      break;

                  case ARITHMETIC_OP_SUB:
                      r = bigger_img->data[index]   - less_img->data[index2];
                      g = bigger_img->data[index+1] - less_img->data[index2+1];
                      b = bigger_img->data[index+2] - less_img->data[index2+2];

                      img_dest->data[index]   = ( r <= 0 ) ? 0 : r;
                      img_dest->data[index+1] = ( g <= 0 ) ? 0 : g;
                      img_dest->data[index+2] = ( b <= 0 ) ? 0 : b;
                      break;

                  case ARITHMETIC_OP_MID:
                      r = ( bigger_img->data[index]   + less_img->data[index2]   ) * 0.5f;
                      g = ( bigger_img->data[index+1] + less_img->data[index2+1] ) * 0.5f;
                      b = ( bigger_img->data[index+2] + less_img->data[index2+2] ) * 0.5f;

                      img_dest->data[index]   = ( r >= 255 ) ? 255 : r;
                      img_dest->data[index+1] = ( g >= 255 ) ? 255 : g;
                      img_dest->data[index+2] = ( b >= 255 ) ? 255 : b;
                      break;

                  default:
                      break;
              }
            }
        }
    }

    glutSetWindow( main_window );
    glutReshapeWindow( img_dest->GetWidth(), img_dest->GetHeight() );
    glutPostRedisplay();
}


#define LOGICAL_OP_AND 0x44
#define LOGICAL_OP_OR  0x45
#define LOGICAL_OP_XOR 0x46

void logical_op_images( int which_op ) {
    PixelMap* bigger_img;
    PixelMap* less_img;

    if( img_dest ) delete img_dest;

    if((img_orig1->GetWidth()*img_orig1->GetHeight()) > (img_orig2->GetWidth()*img_orig2->GetHeight())) {
      bigger_img = img_orig1;
      less_img   = img_orig2;
    }
    else {
      bigger_img = img_orig2;
      less_img   = img_orig1;
    }

    img_dest = new PixelMap();
    data = new uByte[ bigger_img->GetWidth() * bigger_img->GetHeight() * 3 ];
    img_dest->CreateImage( bigger_img->GetWidth(), bigger_img->GetHeight(), data );

    img_orig1->ConvertToGrayScale();
    img_orig2->ConvertToGrayScale();


    for( int y = 0; y < img_dest->GetHeight(); y++) {
        for( int x = 0; x < img_dest->GetWidth(); x++ ) {
          int big_index  = ( y * img_dest->GetWidth() + x ) * 3;
          int less_index = ( y * less_img->GetWidth() + x ) * 3;

          if( (y > less_img->GetHeight()) || (x > less_img->GetWidth()) ) {
            img_dest->data[big_index]   = bigger_img->data[big_index];
            img_dest->data[big_index+1] = bigger_img->data[big_index+1];
            img_dest->data[big_index+2] = bigger_img->data[big_index+2];
          }
          else {
            bool big_bool, less_bool;
            (bigger_img->data[big_index] > 127)? big_bool  = true : big_bool  = false;
            (less_img->data[less_index]  > 127)? less_bool = true : less_bool = false;

            switch(which_op) {
              case LOGICAL_OP_AND:
                if(big_bool && less_bool) {
                  img_dest->data[big_index]   = 255;
                  img_dest->data[big_index+1] = 255;
                  img_dest->data[big_index+2] = 255;
                }
                else {
                  img_dest->data[big_index]   = 0;
                  img_dest->data[big_index+1] = 0;
                  img_dest->data[big_index+2] = 0;
                }
                break;

              case LOGICAL_OP_OR:
                if(big_bool || less_bool) {
                  img_dest->data[big_index]   = 255;
                  img_dest->data[big_index+1] = 255;
                  img_dest->data[big_index+2] = 255;
                }
                else {
                  img_dest->data[big_index]   = 0;
                  img_dest->data[big_index+1] = 0;
                  img_dest->data[big_index+2] = 0;
                }
                break;

              case LOGICAL_OP_XOR:
                if((big_bool || less_bool) && !(big_bool && less_bool)) {
                  img_dest->data[big_index]   = 255;
                  img_dest->data[big_index+1] = 255;
                  img_dest->data[big_index+2] = 255;
                }
                else {
                  img_dest->data[big_index]   = 0;
                  img_dest->data[big_index+1] = 0;
                  img_dest->data[big_index+2] = 0;
                }
                break;

              default: break;
            }
          }
        }
    }

    glutSetWindow( main_window );
    glutReshapeWindow( img_dest->GetWidth(), img_dest->GetHeight() );
    glutPostRedisplay();
}


void rotate_image(){
   if( img_dest ) delete img_dest;

    int width  = img_orig1->GetWidth();
    int height = img_orig1->GetHeight();

    img_dest = new PixelMap();
    data = new uByte[ width * height * 3 ];
    img_dest->CreateImage( width, height, data );

    for( int y = 0; y < img_dest->GetHeight(); y++ )
        for( int x = 0; x < img_dest->GetWidth(); x++ ) {
            int index = ( y * width + x ) * 3;

            int r = 0;
            int g = 0;
            int b = 0;

            r = img_orig1->data[index];
            g = img_orig1->data[index+1];
            b = img_orig1->data[index+2];

            int new_x = x - width/2;
            int new_y = y - height/2;

            int destx = rotationMatrix[0]*new_x + rotationMatrix[1]*new_y;
            int desty = rotationMatrix[2]*new_x + rotationMatrix[3]*new_y;

            destx += width/2;
            desty += height/2;

            if(destx >= width || destx < 0 || desty >= height || desty < 0 ) continue;

            //destx = rotationMatrix[0]*x + rotationMatrix[1]*y;
            //desty = rotationMatrix[2]*x + rotationMatrix[3]*y;

            int dest_index = ( desty * width + destx ) * 3;
            //printf("%d dest index x %d y %d\n", dest_index, destx, desty);

            img_dest->data[dest_index]   = r;
            img_dest->data[dest_index+1] = g;
            img_dest->data[dest_index+2] = b;

			img_dest->data[dest_index+3] = r;
            img_dest->data[dest_index+4] = g;
            img_dest->data[dest_index+5] = b;
        }

    glutSetWindow( main_window );
    glutReshapeWindow( img_dest->GetWidth(), img_dest->GetHeight() );
    glutPostRedisplay();
}

void translate_image(){
   if( img_dest ) delete img_dest;

    int width  = img_orig1->GetWidth();
    int height = img_orig1->GetHeight();

    img_dest = new PixelMap();
    data = new uByte[ width * height * 3 ];
    img_dest->CreateImage( width, height, data );

    for( int y = 0; y < img_dest->GetHeight(); y++ )
        for( int x = 0; x < img_dest->GetWidth(); x++ ) {
            int index = ( y * width + x ) * 3;

            int r = 0;
            int g = 0;
            int b = 0;

            r = img_orig1->data[index];
            g = img_orig1->data[index+1];
            b = img_orig1->data[index+2];

            int destx = x + translationVector[0];
            int desty = y + translationVector[1];
//            if(destx > width || destx < 0 || desty > height || desty < 0 ) continue;
            if(destx >= width || destx < 0 || desty >= height || desty < 0 ) continue;
            int dest_index = ( desty * width + destx ) * 3;
            //printf("%d dest index x %d y %d\n", dest_index, destx, desty);

            img_dest->data[dest_index]   = r;
            img_dest->data[dest_index+1] = g;
            img_dest->data[dest_index+2] = b;
        }

    glutSetWindow( main_window );
    glutReshapeWindow( img_dest->GetWidth(), img_dest->GetHeight() );
    glutPostRedisplay();
}


void scale_image(){
   if( img_dest ) delete img_dest;

    int width  = img_orig1->GetWidth();
    int height = img_orig1->GetHeight();

    img_dest = new PixelMap();
    data = new uByte[ width * height * 3 ];
    img_dest->CreateImage( width, height, data );

    for( int y = 0; y < img_dest->GetHeight(); y++ )
        for( int x = 0; x < img_dest->GetWidth(); x++ ) {
            int index = ( y * width + x ) * 3;

            int r = 0;
            int g = 0;
            int b = 0;

            r = img_orig1->data[index];
            g = img_orig1->data[index+1];
            b = img_orig1->data[index+2];

            int new_x = x - width/2;
            int new_y = y - height/2;

            int destx = new_x * scale;
            int desty = new_y * scale;

            destx += width/2;
            desty += height/2;

            if(destx > width || destx < 0 || desty > height || desty < 0 ) continue;

            int dest_index = ( desty * width + destx ) * 3;
            //printf("%d dest index x %d y %d\n", dest_index, destx, desty);

            img_dest->data[dest_index]   = r;
            img_dest->data[dest_index+1] = g;
            img_dest->data[dest_index+2] = b;
        }

    glutSetWindow( main_window );
    glutReshapeWindow( img_dest->GetWidth(), img_dest->GetHeight() );
    glutPostRedisplay();
}


void glui_callback( int id ) {

    switch( id ) {
        case GLUI_RADIO_OP:

            switch( op ) {
                /// Aritimética
                case 0:
                    panel_op_arithimetic->enable();
                    panel_op_geometric->disable();
                    panel_op_logic->disable();
                    cout << "Radiobutton Operacoes aritmeticas" << endl;
                    break;

                /// Lógica
                case 1:
                    panel_op_logic->enable();
                    panel_op_arithimetic->disable();
                    panel_op_geometric->disable();
                    cout << "Radiobutton Operacoes logicas" << endl;
                    break;

                /// Geométrica
                case 2:
                    panel_op_geometric->enable();
                    panel_op_arithimetic->disable();
                    panel_op_logic->disable();
                    cout << "Radiobutton Operacoes geometricas" << endl;
                    break;


                default:
                    break;
            }

            break;

        /// Opção de translação
        case GLUI_TRANSLATION_OP_GEOMETRIC:
            cout << "Translacao ";
            printf( "%7.4f %7.4f", panel_op_geometric_translation_XY[0], panel_op_geometric_translation_XY[1] );
            translationVector[0] = panel_op_geometric_translation_XY[0];
            translationVector[1] = panel_op_geometric_translation_XY[1];
            translate_image();
            cout << endl;
            break;

        /// Opção de escalamento
        case GLUI_SCALE_OP_GEOMETRIC:
            cout << "Escalamento ";
            printf( "%7.4f", panel_op_geometric_scale[0] );
            scale = panel_op_geometric_scale[0]/100.0;
            scale_image();
            cout << endl;
            break;

        /// Opção de rotação
        case GLUI_ROTATION_OP_GEOMETRIC:
            cout << "Rotacao ";
            printf(
                "%7.4f %7.4f %7.4f %7.4f",
                panel_op_geometric_rotarion_matrix[ 0],
                panel_op_geometric_rotarion_matrix[ 5],
                panel_op_geometric_rotarion_matrix[10],
                panel_op_geometric_rotarion_matrix[15]
            );
            rotationMatrix[0] = panel_op_geometric_rotarion_matrix[ 0];
            float angle = acos(panel_op_geometric_rotarion_matrix[ 0]);
            printf("angle: %f\n", angle);
            rotationMatrix[1] = -sin(angle);
            rotationMatrix[2] = sin(angle);
            rotationMatrix[3] = rotationMatrix[0] ;
            rotate_image();
            break;

        /// Botão de Carregar das operações aritiméticas
        case GLUI_BUTTON_OP_ARITHIMETIC_LOAD:
            cout << "Botao carregar operacoes aritimeticas" << endl;
            if( img_orig1 ) delete img_orig1;
            if( img_orig2 ) delete img_orig2;

            cout << "Imagem Origem 1: " <<  panel_op_arithimetic_image1 << endl;
            img_orig1 = new PixelMap( panel_op_arithimetic_image1 );
            cout << "Imagem Origem 2: " <<  panel_op_arithimetic_image2 << endl;
            img_orig2 = new PixelMap( panel_op_arithimetic_image2 );
            break;

        /// Botão aplicar das operações aritiméticas
        case GLUI_BUTTON_OP_ARITHIMETIC_APPLY:
            cout << "Botao aplicar operacoes aritmeticas" << endl;
            switch( op_arithimetic ) {
                /// Soma
                case 0:
                    arithmetic_op_images( ARITHMETIC_OP_SUM );
                    break;

                /// Subtração
                case 1:
                    arithmetic_op_images( ARITHMETIC_OP_SUB );
                    break;

                /// Média
                case 2:
                    arithmetic_op_images( ARITHMETIC_OP_MID );
                    break;

                default:
                    break;
            }
            break;

        /// Botão de Carregar das operações lógicas
        case GLUI_BUTTON_OP_LOGIC_LOAD:
            cout << "Botao carregar operacoes logicas" << endl;
            if( img_orig1 ) delete img_orig1;
            if( img_orig2 ) delete img_orig2;

            cout << "Imagem Origem 1: " <<  panel_op_logic_image1 << endl;
            img_orig1 = new PixelMap( panel_op_logic_image1 );
            cout << "Imagem Origem 2: " <<  panel_op_logic_image2 << endl;
            img_orig2 = new PixelMap( panel_op_logic_image2 );
            break;

        /// Botão aplicar das operações lógicas
        case GLUI_BUTTON_OP_LOGIC_APPLY:
            cout << "Botao aplicar operacoes logicas" << endl;
            switch( op_logic ) {
                /// And
                case 0:
                    logical_op_images( LOGICAL_OP_AND );
                    break;

                /// Or
                case 1:
                    logical_op_images( LOGICAL_OP_OR );
                    break;

                /// Xor
                case 2:
                    logical_op_images( LOGICAL_OP_XOR );
                    break;

                default:
                    break;
            }
            break;

        /// Botão carregar das operações geométricas
        case GLUI_BUTTON_OP_GEOMETRIC_LOAD:
            cout << "Botao carregar operacoes geometricas" << endl;
            cout << "Imagem Origem: " <<  panel_op_geometric_image << endl;
            if( img_orig1 ) delete img_orig1;
            if( img_dest ) delete img_dest;
            img_orig1 = new PixelMap( panel_op_geometric_image );
            img_dest = new PixelMap( panel_op_geometric_image );
            glutSetWindow(main_window);
            if(img_dest->GetWidth() > 0 && img_dest->GetHeight() > 0){
              glutReshapeWindow(img_dest->GetWidth(), img_dest->GetHeight());
              glutPostRedisplay();
            }
            break;

        default:
            break;

    }

    fflush( stdout );
}



void system_exit() {
    if( img_dest ) delete img_dest;
    if( img_orig1 ) delete img_orig1;
    if( img_orig2 ) delete img_orig2;
}

int main(int argc, char *argv[]) {
    atexit( system_exit );

    glutInit( &argc, argv );

    // Instantiate image object and load image
    //img = new PixelMap( "resources//lena.bmp" );

    glutInitDisplayMode( GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH );
    glutInitWindowPosition( 0,0 );
    main_window = glutCreateWindow( "PixelMap Image Viewer" );
    glutDisplayFunc( display );
    glutKeyboardFunc( key );
    GLUI_Master.set_glutIdleFunc(NULL);

    /**
     * Implementação da interface com GLUI
     */
    glui = GLUI_Master.create_glui( "Control window - Filtros Espaciais", 0, 700, 50 );
    GLUI_Panel *panel_main = glui->add_panel("", GLUI_PANEL_NONE ); /// Add invisible panel to hold rest of controls

    /**
     * Configura painel principal
     */
    panel_operation      = glui->add_panel_to_panel( panel_main, "Operacao" );
    panel_op_arithimetic = glui->add_panel_to_panel( panel_main, "Operacao Aritmetica" );
    panel_op_logic       = glui->add_panel_to_panel( panel_main, "Operacao Logica" );
    panel_op_geometric   = glui->add_panel_to_panel( panel_main, "Operacao Geometrica" );

    panel_operation_radio_group      = glui->add_radiogroup_to_panel( panel_operation, &op, GLUI_RADIO_OP, glui_callback );

    glui->add_radiobutton_to_group( panel_operation_radio_group, "Aritmetica" );
    glui->add_radiobutton_to_group( panel_operation_radio_group, "Logica" );
    glui->add_radiobutton_to_group( panel_operation_radio_group, "Geometrica" );


    /**
     * Configura painel de operações aritiméticas
     */

    glui->add_edittext_to_panel( panel_op_arithimetic, "Imagem 1: ", GLUI_EDITTEXT_TEXT, panel_op_arithimetic_image1, -1, NULL );
    glui->add_edittext_to_panel( panel_op_arithimetic, "Imagem 2: ", GLUI_EDITTEXT_TEXT, panel_op_arithimetic_image2, -1, NULL );

    glui->add_button_to_panel( panel_op_arithimetic, "Carregar", GLUI_BUTTON_OP_ARITHIMETIC_LOAD, glui_callback );

    panel_op_arithimetic_radio_group = glui->add_radiogroup_to_panel( panel_op_arithimetic, &op_arithimetic, GLUI_RADIO_OP_ARITHIMETIC, NULL );

    glui->add_radiobutton_to_group( panel_op_arithimetic_radio_group, "Soma" );
    glui->add_radiobutton_to_group( panel_op_arithimetic_radio_group, "Subtracao" );
    glui->add_radiobutton_to_group( panel_op_arithimetic_radio_group, "Media" );

    glui->add_button_to_panel( panel_op_arithimetic, "Aplicar", GLUI_BUTTON_OP_ARITHIMETIC_APPLY, glui_callback);


    /**
     * Configura painel de operações Lógicas
     */
    glui->add_edittext_to_panel( panel_op_logic, "Imagem 1: ", GLUI_EDITTEXT_TEXT, panel_op_logic_image1, -1, NULL );
    glui->add_edittext_to_panel( panel_op_logic, "Imagem 2: ", GLUI_EDITTEXT_TEXT, panel_op_logic_image2, -1, NULL );

    glui->add_button_to_panel( panel_op_logic, "Carregar", GLUI_BUTTON_OP_LOGIC_LOAD, glui_callback );

    panel_op_logic_radio_group   = glui->add_radiogroup_to_panel( panel_op_logic, &op_logic, GLUI_RADIO_OP_LOGIC, NULL );

    glui->add_radiobutton_to_group( panel_op_logic_radio_group, "E" );
    glui->add_radiobutton_to_group( panel_op_logic_radio_group, "OU" );
    glui->add_radiobutton_to_group( panel_op_logic_radio_group, "XOU" );

    glui->add_button_to_panel( panel_op_logic, "Aplicar", GLUI_BUTTON_OP_LOGIC_APPLY, glui_callback );

    panel_op_logic->disable();


    /**
     * Configura painel de operações Geométricas
     */
    glui->add_edittext_to_panel( panel_op_geometric, "Imagem 1: ", GLUI_EDITTEXT_TEXT, panel_op_geometric_image, -1, NULL );

    glui->add_button_to_panel( panel_op_geometric, "Carregar", GLUI_BUTTON_OP_GEOMETRIC_LOAD, glui_callback );

    glui->add_rotation_to_panel( panel_op_geometric, "Rotacao", panel_op_geometric_rotarion_matrix, GLUI_ROTATION_OP_GEOMETRIC, glui_callback );
    glui->add_translation_to_panel( panel_op_geometric, "Translacao", GLUI_TRANSLATION_XY, panel_op_geometric_translation_XY, GLUI_TRANSLATION_OP_GEOMETRIC, glui_callback );
    glui->add_translation_to_panel( panel_op_geometric, "Escalamento", GLUI_TRANSLATION_Z, panel_op_geometric_scale, GLUI_SCALE_OP_GEOMETRIC, glui_callback );

    panel_op_geometric->disable();


    glutMainLoop();

    return 0;
}
